#include "main.h"
#include "def.h"
#include "sync.h"
#include "util.h"
#include "gui.h"
#include "net.h"

#include <maya/MGlobal.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MFnMesh.h>
#include <maya/MItDependencyNodes.h>
#include <maya/MSelectionList.h>
#include <maya/MEventMessage.h>
#include <maya/MCallbackIdArray.h>
#include <time.h>

#pragma message ("remove when VTRY/VCATCH refactored")
#include "..\Global\globaldef.h"

#pragma message ("todo reorganize 1")
#include "..\CommLib\PacketConven.h"
//extern uint GetObjectVertexFormat(MObject& object);

bool GetNameBuffer(tCharBuffer* pCharBuffer, MString string)
{
	if(!pCharBuffer) 
		return false;

	const char* c_str = string.asChar();
	if(!c_str)
		return false;

	memset(pCharBuffer->buffer, 0, 32);
	memcpy(pCharBuffer->buffer, c_str, strlen(c_str));
	return true;
}

bool GetNameBuffer(tCharBuffer* pCharBuffer, MObject object)
{
	MStatus status;
	MFnDependencyNode objectNode(object, &status);
	if(MAYA_FAIL(status))
		return false;

	return GetNameBuffer(pCharBuffer, objectNode.name());
}

tCharBuffer GetMeshName(MObject& mesh) {

	tCharBuffer buffer = {{'\0'}};
	if(!VUtil::IsMesh(mesh))
		return buffer;

	GetNameBuffer(&buffer, mesh);
	return buffer;
}

bool IsEqual(const char* viewerString, MString& mayaString) {

	bool isEqual = false;
	u_int vsLength = strlen(viewerString);
	u_int msLength = mayaString.length();

	if(vsLength != msLength)
		return isEqual;

	isEqual = (mayaString == (const char*)viewerString);
	return isEqual;
}

MCallbackId		VSyncViewer::g_ViewportSelectionListenerCallback = 0;
long			VSyncViewer::g_SyncTimer = 0;

bool VSyncViewer::InitalizeSelectionListener() {

	VSyncViewer::ReleaseSelectionListener();

	MStatus result;
	VSyncViewer::g_ViewportSelectionListenerCallback = MEventMessage::addEventCallback("SelectionChanged", VSyncViewer::OnChangeSelection, NULL, &result);
	MAYA_RETURN_IF_FAIL(result, false);

	return true;
}

void VSyncViewer::ReleaseSelectionListener() {

	if(VSyncViewer::g_ViewportSelectionListenerCallback >= 0) {
		MEventMessage::removeCallback(VSyncViewer::g_ViewportSelectionListenerCallback);
		VSyncViewer::g_ViewportSelectionListenerCallback = 0;
	}
}

void VSyncViewer::OnChangeSelection(void* clientData)
{

	try {
		
		MObject selectedMesh;
		MObject selectedTransform;
		MSelectionList viewportSelection;

		if(MAYA_FAIL(MGlobal::getActiveSelectionList(viewportSelection)))
			throw tException("Failed to get active selection list.");

		switch(viewportSelection.length()) {

			/*
			The user has selected nothing or multiple objects. 
			The call back is fired so release the plugin selection 
			and refresh the GUI. Currently only selecting one object
			is supported.
			*/			
			default: 
			case 0: {

				if(	VSyncViewer::Selection::Mesh.isNull() == false ||
					VSyncViewer::Selection::Transform.isNull() == false)
				VSyncViewer::Selection::Release();
			
			} break;

			case 1: {

				MObject object;
				if(MAYA_FAIL(viewportSelection.getDependNode(0, object)))
					throw tException("Failed to get selected object handle.");
				
				/*
				If selected object is a transformation node look for it's first
				mesh/shape child and use the first found as the mesh in the 
				current selection
				*/
				MFnDagNode objectDagNode(object);
				MObject tmp;
				if(object.apiType() == MFn::kTransform) {

					for(uint j = 0, n = objectDagNode.childCount(); j < n; j++) {
						
						tmp = objectDagNode.child(j);
						if(tmp.hasFn(MFn::kMesh)) {

							selectedMesh = tmp;
							selectedTransform = object;
							break;
						}
					}
				}
				/*
				If selected object is a mesh node look for it's first
				parent transformation and use the first found as the 
				transform in the current selection
				*/
				else if(object.apiType() == MFn::kMesh) {

					for(uint j = 0, n = objectDagNode.parentCount(); j < n; j++) {

						tmp = objectDagNode.parent(j);
						if(tmp.hasFn(MFn::kTransform)) {

							selectedMesh = object;
							selectedTransform = tmp;
							break;
						}
					}
				}
				else
					throw tException("Selected object handle has wrong type: %s", object.apiTypeStr());
			}
		}

		/*
		If something has been selected 
		*/
		if(!selectedMesh.isNull() && !selectedTransform.isNull()) {

			/*
			Always clear what selection state may alreday exist
			*/
			VSyncViewer::Selection::Release();
			
			/*
			Update the selection state and initalize it accordingly. Only update server
			if nessisary
			*/
			if(VSyncViewer::Selection::Transform != selectedTransform ||
				VSyncViewer::Selection::Mesh != selectedMesh) {

				if(!VSyncViewer::CurrentSelection())
				{ /*tException("Failed to synchronize with server.");*/ }
			}
			VSyncViewer::Selection::Transform = selectedTransform;
			VSyncViewer::Selection::Mesh = selectedMesh;
			
			if(!VSyncViewer::Selection::InitAttributes())
				throw tException("Init attributes error.");

			if(!VSyncViewer::Selection::InitMaterials())
				throw tException("Failed to initalize selection materials.");

			if(!VSyncViewer::Selection::InitCallbacks())
				throw tException("Failed to initalize selection callbacks.");

		}
		/*
		Clear selection state if it exists
		*/
		else if(VSyncViewer::Selection::IsValid()) {

			VSyncViewer::Selection::Release();
		}
		
		/*
		Update the interface to reflect current selection state
		*/
		VGUI::Refresh::CurrentSelection();
		VGUI::Refresh::SelectionParameters();
		VGUI::Refresh::ProceduralPopulation();
		VGUI::Refresh::DeformationParameters();

	} catch(tException exception) {

		VSyncViewer::Selection::Release();

		VGUI::Refresh::CurrentSelection();
		VGUI::Refresh::SelectionParameters();
		VGUI::Refresh::ProceduralPopulation();
		VGUI::Refresh::DeformationParameters();
	}
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tGeometryHeader* pHeader) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	uint localVertexFormat = 0;
	if(!VUtil::GetMeshVertexFormat(targetObject, localVertexFormat))
		return false;

	if(pHeader->vertexFormat != localVertexFormat)
		return false;

	return true;
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tGeometryPartHeader* pHeader, uint partIndex) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	/*
	Test consistancy in shader filename
	*/
	MPlug attributeShader;
	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_shader"), attributeShader)) 
		return false;
	
	MString localShaderString = attributeShader.asString();	
	MStringArray localShaderArray;
	localShaderString.split(';', localShaderArray);

	if(partIndex > localShaderArray.length() - 1)
		return false;

	if(!IsEqual(pHeader->shader.buffer, localShaderArray[partIndex]))
		return false;

	/*
	Test consistancy in shader triangle assignment
	*/
	MFnMesh targetMesh(targetObject);
	MObjectArray shaderArray;
	MIntArray shaderIndicies;
	if(MAYA_FAIL(targetMesh.getConnectedShaders(0, shaderArray, shaderIndicies)))
		return false;

	if(partIndex > shaderArray.length() - 1 || partIndex > shaderIndicies.length() - 1)
		return false;

	uint shaderFaces = shaderIndicies[partIndex];
	if(shaderFaces != pHeader->triangeCount)
		return false;

	return true;
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tChannelHeader* pHeader, uint partIndex) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	MString localChannelString;
	MString localPartString;
	MStringArray localChannelArray;
	MStringArray localPartArray;

	MString channels[] = {
		MString("liveview_color"),
		MString("liveview_normal"),
		MString("liveview_specular"),
	};
	
	MPlug attribute;
	for(uint i = 0; i < 3; i++) {

		if(!VUtil::GetObjectAttribute(targetObject, channels[i], attribute)) 
			return false;

		localChannelString = attribute.asString();
		localChannelString.split(';', localChannelArray);

		if(partIndex > localChannelArray.length() - 1)
			return false;

		localPartString = localChannelArray[partIndex];
		localPartString.split(',', localPartArray);
		
		QuerysStructs::tChannelHeader* pChannel = &pHeader[i];
		for(uint i = 0; i < 5; i++) {
		
			if(!IsEqual(pChannel->samplers[i].buffer, localPartArray[i]))
				return false;		
		}

	}
	return true;
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tInstancingHeader* pHeader) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	MPlug attribute;
	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_instanced"), attribute)) {

		return (pHeader->isInstanced == false);
	}

	bool localInstanced = (attribute.asInt() != 0);
	if(pHeader->isInstanced != localInstanced)
		return false;

	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_instancechildren"), attribute))
		return false;

	MString localInstanceChildrenString = attribute.asString();
	MStringArray localInstanceChildrenArray;
	localInstanceChildrenString.split(';', localInstanceChildrenArray);

	if(localInstanceChildrenArray.length() + 1 != pHeader->childCount)
		return false;

	return true;
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tInstanceChildHeader* pHeader, uint childIndex) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	MPlug attribute;
	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_instancechildren"), attribute)) 
		return false;

	MString localInstancesString = attribute.asString();
	MStringArray localInstancesArray;
	localInstancesString.split(';', localInstancesArray);

	if(childIndex > localInstancesArray.length() - 1)
		return false;

	MString localInstanceString = localInstancesArray[childIndex];
	MStringArray localInstanceArray;
	localInstanceString.split(',', localInstanceArray);
	
	MString instanceName = localInstanceArray[0];
	if(!IsEqual(pHeader->name.buffer, instanceName))
		return false;

	return true;
}

bool VSyncViewer::CheckMatch(MObject& targetObject, QuerysStructs::tDeformationHeader* pHeader) {

	if(!VUtil::IsMesh(targetObject) || !pHeader)
		return false;
	
	MPlug attribute;
	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_deformation"), attribute)) {

		return (pHeader->isDeformation == false);
	}

	bool localDeformation = (attribute.asInt() != 0);
	if(pHeader->isDeformation != localDeformation)
		return false;

	if(!VUtil::GetObjectAttribute(targetObject, MString("liveview_deformation_scale"), attribute))
		return false;

	float localDeformationScale = attribute.asFloat();
	if(pHeader->scale != localDeformationScale)
		return false;

	return true;
}

bool VSyncViewer::IsObjectSynchronized(MObject& targetObject, VSyncViewer::tSyncReport* pReport) {

	byte* pData = NULL;
	QuerysStructs::tViewerInfoQuery queryMeshDeformation;

	try {

		if(!VUtil::IsMesh(targetObject) || !pReport)
			throw tException("Invalid parameters.");

		MString targetName;
		if(!VUtil::GetObjectName(targetObject, targetName))
			throw tException("Failed to get query target.");

		queryMeshDeformation.responceType = QUERY_RESPONCE_SYNCHRONIZED;
		if(!GetNameBuffer(&queryMeshDeformation.target, targetName))
			throw tException("Failed to get query target.");

		UINT dataSize = 0;
		if(!VNet::QueryServer(queryMeshDeformation, &pData, &dataSize))
			throw tException("Failed to query server.");

		if(dataSize < sizeof(QuerysStructs::tQueryHeader))
			throw tException("Invalid query data stream.");
		
		QuerysStructs::tQueryHeader* pHeader = (QuerysStructs::tQueryHeader*)pData;
		pData += sizeof(QuerysStructs::tQueryHeader);

		if(!IsEqual(pHeader->targetName.buffer, targetName))
			throw tException("Query mismatch occoured.");
		
		/*
		If the server reports that this object is present on it, then do a more comprehensive
		analysis to determine what actually needs to be updated
		*/
		if(pHeader->isPresent) {

			/*
			I'm an optimist
			*/
			pReport->IsDeformation = true;
			pReport->IsGeometry = true;
			pReport->IsInstancing = true;
			pReport->IsParameters = true;

			/*
			Check geometry
			*/
			QuerysStructs::tGeometryHeader* pGeometry = (QuerysStructs::tGeometryHeader*)pData;
			if((uint)pGeometry - (uint)pHeader >= dataSize)
				throw tException("Geometry query data expected but not fully present.");

			pReport->IsGeometry = CheckMatch(targetObject, pGeometry);
			pData += sizeof(QuerysStructs::tGeometryHeader);

			/*
			Check geometry parts and assigned materials
			*/
			for(uint i = 0; i < (uint)max(0, pGeometry->partCount); i++) {

				QuerysStructs::tGeometryPartHeader* pGeometryPart = (QuerysStructs::tGeometryPartHeader*)pData;
				if((uint)pGeometryPart - (uint)pHeader >= dataSize)
					throw tException("Geometry part query data expected but not fully present.");

				if(!CheckMatch(targetObject, pGeometryPart, i))
					pReport->IsGeometry = false;

				if(!CheckMatch(targetObject, pGeometryPart->channel, i))
					pReport->IsParameters = false;

				pData += sizeof(QuerysStructs::tGeometryPartHeader);
			}

			/*
			Check instancing
			*/
			QuerysStructs::tInstancingHeader* pInstancing = (QuerysStructs::tInstancingHeader*)pData;
			if((uint)pInstancing - (uint)pHeader >= dataSize)
				throw tException("Instancing query data expected but not fully present.");

			pReport->IsInstancing = CheckMatch(targetObject, pInstancing);
			pData += sizeof(QuerysStructs::tInstancingHeader);

			/*
			Check instance children
			*/
			for(uint i = 0; i < (uint)max(0, pInstancing->childCount); i++) {

				QuerysStructs::tInstanceChildHeader* pInstanceChild = (QuerysStructs::tInstanceChildHeader*)pData;
				if((uint)pInstanceChild - (uint)pHeader >= dataSize)
					throw tException("Instance child query data expected but not fully present.");

				if(!CheckMatch(targetObject, pInstanceChild, i))
					pReport->IsInstancing = false;
				pData += sizeof(QuerysStructs::tInstanceChildHeader);
			}

			/*
			Check deformation
			*/
			QuerysStructs::tDeformationHeader* pDeformation = (QuerysStructs::tDeformationHeader*)pData;
			if((uint)pDeformation - (uint)pHeader >= dataSize)
				throw tException("Deformation query data expected but not fully present.");

			pReport->IsDeformation = CheckMatch(targetObject, pDeformation);
			pData += sizeof(QuerysStructs::tDeformationHeader);

		} 
		/*
		The target object doesn't exist on the server so signal that everything is out of sync
		*/
		else {

			pReport->IsDeformation = false;
			pReport->IsGeometry = false;
			pReport->IsInstancing = false;
			pReport->IsParameters = false;
		}

		if(pData)
			delete [] pData;

		return true;

	}catch(tException exception) {

		if(pData)
			delete [] pData;

		return false;
	}
}

/**
	Ensures the scene is send to the server to ensure consistancy between Maya and it.

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool VSyncViewer::EntireScene() {

	try {
		
		for(MItDependencyNodes it(MFn::kMesh);!it.isDone();it.next()) {

			MObject object = it.item();
			VSyncViewer::Selection::Mesh = object;
			VUtil::GetMeshTransform(object, VSyncViewer::Selection::Transform);
			
			if(!VSyncViewer::CurrentSelection())
				throw tException("Failed to synchronize an object.");
		}

		VSyncViewer::Selection::Release();
		if(MAYA_FAIL(MGlobal::setActiveSelectionList(MSelectionList(), MGlobal::kReplaceList)))
			throw tException("Failed to reset scene selection.");

		return true;

	}catch(tException exception) {

		return false;
	}
}

/**
	Ensures that the server consistatly reflects the current selection in Maya.

	@author Dacre Denny
	@return true if successful otherwise false
*/
bool VSyncViewer::CurrentSelection() {

	try {
	
		/*
		Check some basic preconditions
		*/
		if(!VSyncViewer::Selection::IsValid())
			throw tException("Invalid viewport selection.");
	
		MObject targetObject = VSyncViewer::Selection::Mesh;
		if(!VUtil::IsMesh(targetObject))
			throw tException("Invalid object type: %s", targetObject.apiTypeStr());

		/*
		Create a synchronization report, and query the server. If successful
		review the report to determine what, if anything in the current selection,
		needs to be updated on the server
		*/
		tSyncReport report;
		if(!VSyncViewer::IsObjectSynchronized(targetObject, &report))
			throw tException("Failed to establish synchronization state.");

		if(!report.IsGeometry) {
				
			if(!VNet::SendGeometry(targetObject))
				throw tException("Failed to send mesh data.");	
		}

		if(!report.IsParameters) {
			
			if(!VNet::SendParameters(targetObject))
				throw tException("Failed to send parameter data.");

			if(!VNet::SendSamplers(targetObject))
				throw tException("Failed to send sampler data.");
		}

		if(!report.IsInstancing) {
				
			if(!VNet::SendInstancing(targetObject))
				throw tException("Failed to send instancing data.");
		}

		if(!report.IsDeformation) {
				
			if(!VNet::SendDeformation(targetObject))
				throw tException("Failed to send deformation data.");	
		}

		/*
		Always send a transformation because it's quite cheap to do
		*/
		if(!VNet::SendTransform(targetObject, VSyncViewer::Selection::Transform))
			throw tException("Failed to send transform data.");	
		
		return true;
	}
	catch(tException exception) {

		cout<<"Error: VSyncViewer::CurrentSelection() "<< exception.Message.c_str() <<endl;
		return false;
	}
}


void VSyncViewer::CallbackWrappers::RemoveObjectCallbacks(MObject& object) {

	MCallbackIdArray nodeCallbacks;
	if(MAYA_FAIL(MEventMessage::nodeCallbacks(object, nodeCallbacks)))
		return;

	if(nodeCallbacks.length() > 0) {

		if(MAYA_FAIL(MEventMessage::removeCallbacks(nodeCallbacks)))
			return;
	}

	if(MAYA_FAIL(MNodeMessage::nodeCallbacks(object, nodeCallbacks)))
		return;

	if(nodeCallbacks.length() > 0) {

		if(MAYA_FAIL(MNodeMessage::removeCallbacks(nodeCallbacks)))
			return;
	}

}

/**
	Called when the user alters an attribute value in a sampler that has some relation to the live view system. Takes care of calling functionality
	that relates to the samplers purpose

	@author Dacre Denny
*/
void VSyncViewer::CallbackWrappers::OnChangeSampler(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug& otherPlug, void* pClientData) {
	
	if(msg & MNodeMessage::kConnectionBroken) {

		VSyncViewer::CallbackWrappers::RemoveObjectCallbacks(plug.node());
	}
	else {

		MObject samplerObject = plug.node();
		MString samplerNameString;
		VUtil::GetObjectName(samplerObject, samplerNameString);
		
		MStringArray samplerNameArray;
		samplerNameString.split('_', samplerNameArray);

		/*
		Looks like the sampler thats causing this callback is used for instancing
		*/
		if(samplerNameArray[0] == MString("instance")) {

			if(samplerNameArray.length() != 5)
				return;

			MObject targetObject;
			if(!VUtil::GetMeshByName(samplerNameArray[1], targetObject))
				return;

			MString childObject = samplerNameArray[2];

			MString channelName;
			switch(samplerNameArray[3].asInt()) {

				case 0:
					channelName = MString("liveview_position");
					break;
				case 1:
					channelName = MString("liveview_scale");
					break;
				case 2:
					channelName = MString("liveview_rotation");
					break;
				default:
					return;
			}
			VNet::SendSampler(targetObject, samplerObject, childObject, channelName);

		}
		/*
		Divert this callback update to a standard surface sampler
		*/
		else {		

			if(samplerNameArray.length() != 5)
				return;

			MObject targetObject;
			if(!VUtil::GetMeshByName(samplerNameArray[0], targetObject))
				return;

			int partIndex = samplerNameArray[1].asInt();
			/*
			Index of 3 accounts for liveview_ part that'll be clipped
			*/
			MString materialChannel = MString("liveview_") + samplerNameArray[3]; 
			VNet::SendSampler(targetObject, samplerObject, partIndex, materialChannel);
		}

		//VSyncViewer::g_SyncTimer = currentTime;
	}
}

void VSyncViewer::CallbackWrappers::OnChangeTransform(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug& otherPlug, void* pClientData) {

	if(msg & MNodeMessage::kConnectionBroken) {

		VSyncViewer::CallbackWrappers::RemoveObjectCallbacks(plug.node());
	}
	else {
		
		MObject mesh;
		MObject transform;
		if(VUtil::IsMesh(plug.node())) {
	
			mesh = plug.node();
			if(!VUtil::GetMeshTransform(mesh, transform))
				return;
		}
		else if(VUtil::IsTransform(plug.node())) {

			transform = plug.node();
			if(!VUtil::GetTransformMesh(transform, mesh))
				return;
		}
		
		VNet::SendTransform(mesh, transform);
	}
}

void VSyncViewer::CallbackWrappers::OnChangeGeometry(MNodeMessage::AttributeMessage msg, MPlug& plug, MPlug& otherPlug, void* pClientData) {
	
	if(msg & MNodeMessage::kConnectionBroken) {

		VSyncViewer::CallbackWrappers::RemoveObjectCallbacks(plug.node());
	}
	else {
	/*clock_t currentTime = clock();
	if((currentTime - VSyncViewer::g_SyncTimer) > VSyncViewer::g_SyncThrottle)*/ 

		//VSyncViewer::g_SyncTimer = currentTime;
		VNet::SendGeometry(plug.node());
	}
}

void VSyncViewer::CallbackWrappers::SceneRemoveObject(MObject& object, void* pClientData) {
	
	if(VUtil::IsTransform(object)) {

		MObject meshObject;
		if(VUtil::GetTransformMesh(object, meshObject))
			VNet::SendMeshRemoved(meshObject);
	}
	else {

		VNet::SendMeshRemoved(object);
	}
}